package image

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"os"
	"strings"

	"cloudea.com/container/config"
	"cloudea.com/container/utils"
)

/** 镜像配置文件中的层信息 */
type ImageManifestLayer struct {
	BlobSum string
}

/** 镜像配置文件中的历史信息 */
type ImageManifestHistory struct {
	V1Compatibility string
}

/** 镜像配置文件中的运行时信息 */
type ImageManifestHistoryV1 struct {
	Architecture string
	Config       ImageManifestHistoryV1Config
	Container    string
}

/** 镜像配置文件中的运行时信息配置 */
type ImageManifestHistoryV1Config struct {
	Env        []string
	Cmd        []string
	Entrypoint []string
	WorkingDir string
	Image      string
}

/** 镜像配置文件 */
type ImageManifest struct {
	Name     string
	Tag      string
	FsLayers []ImageManifestLayer
	History  []ImageManifestHistory
}

/** 错误信息 */
type ImageError struct {
	Code    string
	Message string
}

type ImageErrors struct {
	Errors []ImageError
}

/** 从磁盘获取一个Image对象*/
func ImageGet(name string, tag string) (*Image, error) {
	return ImageGetByManifest(config.GetImageConfig(name, tag))
}

/** 通过配置文件获取镜像 */
func ImageGetByManifest(file string) (*Image, error) {
	manifest, err := readManifest(file)
	if err != nil {
		return nil, err
	}
	fileInfo, err := os.Stat(file)
	if err != nil {
		return nil, err
	}
	if len(manifest.History) == 0 {
		return nil, errors.New("镜像缺少运行时信息")
	}

	// 解析运行时信息
	var v1 ImageManifestHistoryV1
	err = json.Unmarshal([]byte(manifest.History[0].V1Compatibility), &v1)
	if err != nil {
		return nil, err
	}

	// 解析层
	manifest.Name, _ = strings.CutPrefix(manifest.Name, "library/")
	var layers []string
	for _, fsLayer := range manifest.FsLayers {
		layers = append(layers, fsLayer.BlobSum)
	}

	return &Image{
		manifest.Name,
		manifest.Tag,
		fileInfo.ModTime(),
		layers,
		v1.Config.Entrypoint,
		v1.Config.Cmd,
		v1.Config.Env,
		v1.Config.WorkingDir,
		v1.Config.Image,
	}, nil
}

/** 获取镜像列表 */
func ImageList() []*Image {
	var images []*Image
	files, err := utils.GetAllFiles(config.IMAGE_PATH)
	if err != nil {
		return images
	}
	for _, file := range files {
		if strings.HasSuffix(file, config.MANIFEST) {
			image, err := ImageGetByManifest(file)
			if err != nil {
				continue
			}
			images = append(images, image)
		}
	}
	return images
}

/** 从磁盘删除镜像 */
func ImageRemove(image *Image) error {
	containerBasePath := config.GetImageContainers(image.name, image.tag)

	dir, err := os.ReadDir(containerBasePath)
	if err != nil {
		return err
	}
	isEmpty := true
	for _, fi := range dir {
		stat, err := os.Lstat(containerBasePath + "/" + fi.Name())
		if err == nil && (stat.Mode()&os.ModeSymlink != 0) {
			isEmpty = false
			break
		}
	}
	if !isEmpty {
		return errors.New("请先删除该镜像的所有容器")
	}

	// 删除layers
	imageManifestFile := config.GetImageConfig(image.name, image.tag)
	// manifest, err := readManifest(imageManifestFile)
	// if err != nil {
	// 	return err
	// }

	// for _, layer := range manifest.FsLayers {
	// 	layerPath := config.GetImageLayerBase(layer.BlobSum)
	// 	err := os.RemoveAll(layerPath)
	// 	if err != nil {
	// 		return err
	// 	}
	// }

	// 删除manifest
	err = os.RemoveAll(imageManifestFile)
	if err != nil {
		return err
	}
	return nil
}

/** 拉取镜像并保存到磁盘 */
func ImageSave(image *Image) error {

	if !verifyVersion() {
		panic("仓库不支持v2 API")
	}
	// 准备目录
	imageBasePath := config.GetImageBase(image.name, image.tag)

	defer func() {
		// 发生宕机时，获取panic传递的上下文并打印，类似于catch
		err := recover()
		if err != nil {
			// 删除已经创建的文件和目录
			os.RemoveAll(imageBasePath)
			panic(err)
		}
	}()

	imageManefestURL := config.DOCKER_HUB + fmt.Sprintf("/v2/library/%s/manifests/%s", image.name, image.tag)
	imageManifestFile := config.GetImageConfig(image.name, image.tag)

	im, _ := ImageGet(image.name, image.tag)
	if im == nil {
		fmt.Println("正在下载配置文件")
		utils.Download(imageManefestURL, imageManifestFile)
		fmt.Println("配置文件下载完成")
	}

	// 解析json文件
	manifest, err := readManifest(imageManifestFile)
	if err != nil {
		return nil
	}
	// 下载layer
	finished := make(chan error, len(manifest.FsLayers))
	for i := 0; i < len(manifest.FsLayers); i++ {
		go downloadLayer(i+1, image.name, manifest.FsLayers[i].BlobSum, finished)
	}
	for i := 0; i < len(manifest.FsLayers); i++ {
		status := <-finished
		if status == nil {
			fmt.Printf("已拉取%d个分层\n", i+1)
		} else {
			return errors.New(fmt.Sprintf("拉取%d个分层失败: %v\n", i+1, status))
		}
	}

	return nil
}

/** 检察是否支持v2的api */
func verifyVersion() bool {
	resp, err := http.Get(config.DOCKER_HUB + "/v2/")
	if err != nil {
		panic(err)
	}

	return resp.StatusCode == 200
}

/** 读取配置文件 */
func readManifest(imageManifestFile string) (*ImageManifest, error) {
	content, err := os.ReadFile(imageManifestFile)
	if err != nil {
		return nil, err
	}
	var manifest ImageManifest
	var manifestError ImageErrors
	err = json.Unmarshal(content, &manifest)
	err2 := json.Unmarshal(content, &manifestError)
	if err != nil {
		return nil, err
	}
	if err2 != nil {
		return nil, err
	}
	if manifestError.Errors != nil {
		if len(manifestError.Errors) >= 1 {
			for _, v := range manifestError.Errors {
				return nil, errors.New(v.Message)
			}
		} else {
			return nil, errors.New("unknow, 可能是tag不存在")
		}
	}
	return &manifest, nil
}

/** 下载一层 */
func downloadLayer(index int, name string, digest string, finished chan error) {
	layerBlobURL := config.DOCKER_HUB + fmt.Sprintf("/v2/library/%s/blobs/%s", name, digest)
	layerBlobDest := config.GetImageTar(digest)
	layerDeCompressPath := config.GetImageLayerBase(digest)
	if !utils.FileExists(layerBlobDest) {
		utils.Download(layerBlobURL, layerBlobDest)
	}
	err := utils.DeCompressTar(layerBlobDest, layerDeCompressPath)
	if err != nil {
		finished <- err
	}
	finished <- nil
}
